using EntityFramework.Extensions;
using Nop.Core;
using Nop.Core.Data;
using Nop.Core.Domain.Catalog;
using Nop.Core.Domain.Customers;
using Nop.Core.Domain.Logistics;
using Nop.Core.Domain.Orders;
using Nop.Core.Domain.Payments;
using Nop.Core.Domain.Shipping;
using Nop.Core.Domain.Stores;
using Nop.Services.Events;
using System;
using System.Collections.Generic;
using System.Linq;
using Org.BouncyCastle.Security;

namespace Nop.Services.Orders
{
    /// <summary>
    /// Order service
    /// </summary>
    public partial class OrderService : IOrderService
    {
        #region Fields

        private readonly IRepository<Order> _orderRepository;
        private readonly IRepository<OrderProductVariant> _opvRepository;
        private readonly IRepository<ShipmentOrderProductVariant> _sopvRepository;
        private readonly IRepository<OrderNote> _orderNoteRepository;
        private readonly IRepository<ProductVariant> _pvRepository;
        private readonly IRepository<RecurringPayment> _recurringPaymentRepository;
        private readonly IRepository<Customer> _customerRepository;
        private readonly IRepository<ReturnRequest> _returnRequestRepository;
        private readonly IRepository<Product> _productRepository;
        private readonly IRepository<Shipment> _shipmentRepository;
        private readonly IRepository<LogisticsPackage> _packageRepository;
        private readonly IEventPublisher _eventPublisher;

        #endregion

        #region Ctor

        /// <summary>
        /// Ctor
        /// </summary>
        /// <param name="orderRepository">Order repository</param>
        /// <param name="opvRepository">Order product variant repository</param>
        /// <param name="orderNoteRepository">Order note repository</param>
        /// <param name="pvRepository">Product variant repository</param>
        /// <param name="recurringPaymentRepository">Recurring payment repository</param>
        /// <param name="customerRepository">Customer repository</param>
        /// <param name="returnRequestRepository">Return request repository</param>
        /// <param name="productRepository">Product repository</param>
        /// <param name="eventPublisher">Event published</param>
        public OrderService(IRepository<Order> orderRepository,
            IRepository<OrderProductVariant> opvRepository,
            IRepository<ShipmentOrderProductVariant> sopvRepository,
            IRepository<OrderNote> orderNoteRepository,
            IRepository<ProductVariant> pvRepository,
            IRepository<RecurringPayment> recurringPaymentRepository,
            IRepository<Customer> customerRepository,
            IRepository<ReturnRequest> returnRequestRepository,
            IRepository<Product> productRepository,
            IRepository<Shipment> shipmentRepository,
            IRepository<LogisticsPackage> packageRepository,
            IEventPublisher eventPublisher)
        {
            _orderRepository = orderRepository;
            _opvRepository = opvRepository;
            _sopvRepository = sopvRepository;
            _orderNoteRepository = orderNoteRepository;
            _pvRepository = pvRepository;
            _recurringPaymentRepository = recurringPaymentRepository;
            _customerRepository = customerRepository;
            _returnRequestRepository = returnRequestRepository;
            _eventPublisher = eventPublisher;
            _productRepository = productRepository;
            _shipmentRepository = shipmentRepository;
            _packageRepository = packageRepository;
        }

        #endregion

        #region Methods

        #region Orders

        /// <summary>
        /// Gets the store ID
        /// </summary>
        /// <param name="orderId">The order identifier</param>
        /// <returns>store ID</returns>
        public virtual int GetStoreIdByOrderId(int orderId)
        {
            if (orderId == 0)
                return 0;
            var query = from p in _productRepository.Table
                        join pv in _pvRepository.Table on p.Id equals pv.ProductId
                        join opv in _opvRepository.Table on pv.Id equals opv.ProductVariantId
                        join o in _orderRepository.Table on opv.OrderId equals o.Id
                        where o.Id == orderId
                        select p;

            return (int)query.FirstOrDefault().StoreId;
        }

        /// <summary>
        /// Gets the store
        /// </summary>
        /// <param name="orderId">The order identifier</param>
        /// <returns>Store</returns>
        public virtual Store GetStoreByOrderId(int orderId)
        {
            if (orderId == 0)
                return null;
            var query = from p in _productRepository.Table
                        join pv in _pvRepository.Table on p.Id equals pv.ProductId
                        join opv in _opvRepository.Table on pv.Id equals opv.ProductVariantId
                        join o in _orderRepository.Table on opv.OrderId equals o.Id
                        where o.Id == orderId
                        select p.Store;

            return query.FirstOrDefault();
        }

        /// <summary>
        /// Gets an order
        /// </summary>
        /// <param name="orderId">The order identifier</param>
        /// <returns>Order</returns>
        public virtual Order GetOrderById(int orderId)
        {
            if (orderId == 0)
                return null;

            return _orderRepository.GetById(orderId);
        }

        /// <summary>
        /// Gets an order
        /// </summary>
        /// <param name="boundOrderId">the order boundOrder Id</param>
        /// <returns>Order</returns>
        public virtual Order GetOrderByBoundOrderId(int boundOrderId)
        {
            if (boundOrderId <= 0) return null;

            return _orderRepository.Table.FirstOrDefault(d => d.BoundOrderId == boundOrderId);
        }

        /// <summary>
        /// Get orders by identifiers
        /// </summary>
        /// <param name="orderIds">Order identifiers</param>
        /// <param name="storeId">The store ID; 1 to load all orders</param>
        /// <returns>Order</returns>
        public virtual IList<Order> GetOrdersByIds(int[] orderIds, int storeId = 1)
        {
            if (orderIds == null || orderIds.Length == 0)
                return new List<Order>();

            var query = from o in _orderRepository.Table
                        where orderIds.Contains(o.Id)
                        select o;

            if (storeId > 1)
            {
                query = from o in query
                        join opv in _opvRepository.Table on o.Id equals opv.OrderId
                        join pv in _pvRepository.Table on opv.ProductVariantId equals pv.Id
                        join p in _productRepository.Table on pv.ProductId equals p.Id
                        where p.StoreId == storeId
                        select o;
            }

            var orders = query.ToList();
            //sort by passed identifiers
            var sortedOrders = new List<Order>();
            foreach (int id in orderIds)
            {
                var order = orders.Find(x => x.Id == id);
                if (order != null)
                    sortedOrders.Add(order);
            }
            return sortedOrders;
        }

        /// <summary>
        /// Gets an order
        /// </summary>
        /// <param name="orderGuid">The order identifier</param>
        /// <returns>Order</returns>
        public virtual Order GetOrderByGuid(Guid orderGuid)
        {
            if (orderGuid == Guid.Empty)
                return null;

            var query = from o in _orderRepository.Table
                        where o.OrderGuid == orderGuid
                        select o;
            var order = query.FirstOrDefault();
            return order;
        }

        /// <summary>
        /// Deletes an order
        /// </summary>
        /// <param name="order">The order</param>
        public virtual void DeleteOrder(Order order)
        {
            if (order == null)
                throw new ArgumentNullException("order");

            order.Deleted = true;
            UpdateOrder(order);
        }

        /// <summary>
        /// Search orders 
        /// </summary>
        /// <param name="startTime">Order start time; null to load all orders</param>
        /// <param name="endTime">Order end time; null to load all orders</param>
        /// <param name="os">Order status; null to load all orders</param>
        /// <param name="pss">An array of order payment status; null to load all orders only for express orders</param>
        /// <param name="ss">Order shippment status; null to load all orders</param>
        /// <param name="billingEmail">Billing email. Leave empty to load all records.</param>
        /// <param name="orderGuid">Search by order GUID (Global unique identifier) or part of GUID. Leave empty to load all orders.</param>
        /// <param name="pageIndex">Page index</param>
        /// <param name="pageSize">Page size</param>
        /// <param name="excludedOrderIds">an array of order IDs to exclude from search</param>
        /// <param name="storeId">store identifier, 1 for all store results</param>
        /// <param name="districtId">district; null to load all orders</param>
        /// <param name="orderKind">Order kind; null to load all orders</param>
        /// <param name="excludeInvoiceAssigned">Exclude orders have invoices assigned; false to load all orders, true to load order without invoice assigned</param>
        /// <param name="hasGiftcardHistory">Has gift history within orders; null to load all orders</param>
        /// <param name="pickUpTime">pick up time</param>
        /// <returns>Order collection</returns>
        public virtual IPagedList<Order> SearchOrders(DateTime? startTime, DateTime? endTime,
            OrderStatus? os, PaymentStatus[] pss, ShippingStatus? ss, string billingEmail,
            string orderGuid, int pageIndex, int pageSize, int[] excludedOrderIds = null, int storeId = 1,
            int? districtId = null, OrderKind? orderKind = null, bool excludeInvoiceAssigned = false, bool? hasGiftcardHistory = null, DateTime? pickUpTime = null)
        {

            var query = QueryCondition(startTime, endTime, os, pss, ss, billingEmail, orderGuid, excludedOrderIds, storeId, districtId, orderKind, excludeInvoiceAssigned, hasGiftcardHistory, pickUpTime);

            return new PagedList<Order>(query, pageIndex, pageSize);
        }

        private IQueryable<Order> QueryCondition(DateTime? startTime, DateTime? endTime,
            OrderStatus? os, PaymentStatus[] pss, ShippingStatus? ss, string billingEmail,
            string orderGuid, int[] excludedOrderIds = null, int storeId = 1,
            int? districtId = null, OrderKind? orderKind = null, bool excludeInvoiceAssigned = false, bool? hasGiftcardHistory = null, DateTime? pickUpTime = null)
        {
            int? orderStatusId = null;
            if (os.HasValue)
                orderStatusId = (int)os.Value;

            int? shippingStatusId = null;
            if (ss.HasValue)
                shippingStatusId = (int)ss.Value;

            int? orderKindId = null;
            if (orderKind.HasValue)
                orderKindId = (int)orderKind.Value;

            var query = _orderRepository.Table;
            query = query.Where(d => !d.Deleted);

            if (!string.IsNullOrEmpty(orderGuid))
            {
                var guid = Guid.Parse(orderGuid.ToLower());
                query = query.Where(d => d.OrderGuid == guid);
            }

            if (storeId > 1)
            {
                //query = (from o in _orderRepository.Table
                //         join opv in _opvRepository.Table on o.Id equals opv.OrderId
                //         join pv in _pvRepository.Table on opv.ProductVariantId equals pv.Id
                //         join p in _productRepository.Table on pv.ProductId equals p.Id
                //         where p.StoreId == storeId && pv.Deleted == false && p.Deleted == false && o.Deleted == false
                //         select o).Distinct();

                query = from o in query
                        where o.StoreId.Value == storeId
                        select o;
            }

            if (startTime.HasValue)
                query = query.Where(o => startTime.Value <= o.CreatedOnUtc);
            if (endTime.HasValue)
                query = query.Where(o => endTime.Value >= o.CreatedOnUtc);
            if (orderStatusId.HasValue)
                query = query.Where(o => orderStatusId.Value == o.OrderStatusId);
            if (pss != null && pss.Length > 0)
                query = query.Where(o => pss.Cast<int>().Contains(o.PaymentStatusId));
            if (shippingStatusId.HasValue)
                query = query.Where(o => shippingStatusId.Value == o.ShippingStatusId);
            if (!String.IsNullOrEmpty(billingEmail))
                query = query.Where(o => o.BillingAddress != null && !String.IsNullOrEmpty(o.BillingAddress.Email) && o.BillingAddress.Email.Contains(billingEmail));
            if (districtId.HasValue)
                query = query.Where(o => o.DistrictId == districtId.Value);
            if (orderKindId.HasValue)
                query = query.Where(o => o.OrderKindId == orderKindId.Value);

            if (excludedOrderIds != null && excludedOrderIds.Length > 0)
                query = query.Where(o => !excludedOrderIds.Contains(o.Id));

            if (excludeInvoiceAssigned)
                query = query.Where(o => o.InvoiceId == null);

            if (hasGiftcardHistory.HasValue)
            {
                if (hasGiftcardHistory.Value)
                {
                    query = query.Where(o => o.GiftCardUsageHistory.Count() > 0);
                }
                else
                {
                    query = query.Where(o => o.GiftCardUsageHistory.Count() == 0);
                }
            }

            if (pickUpTime != null)
            {
                var pickDate = pickUpTime.Value.Date;
                query = query.Where(d => d.ShipmentDate == pickDate);
            }

            query = query.OrderByDescending(o => o.CreatedOnUtc);

            return query;
        }


        /// <summary>
        /// Search orders (express and commerce)
        /// </summary>
        /// <param name="startTime">Order start time; null to load all orders</param>
        /// <param name="endTime">Order end time; null to load all orders</param>
        /// <param name="os">Order status; null to load all orders</param>
        /// <param name="pss">An array of order payment status; null to load all orders only for express orders</param>
        /// <param name="ss">Order shippment status; null to load all orders</param>
        /// <param name="billingEmail">Billing email. Leave empty to load all records.</param>
        /// <param name="orderGuid">Search by order GUID (Global unique identifier) or part of GUID. Leave empty to load all orders.</param>
        /// <param name="excludedOrderIds">an array of order IDs to exclude from search</param>
        /// <param name="storeId">store identifier, 1 for all store results</param>
        /// <param name="districtId">district; null to load all orders</param>
        /// <param name="orderKind">Order kind; null to load all orders</param>
        /// <param name="excludeInvoiceAssigned">Exclude orders have invoices assigned; false to load all orders, true to load order without invoice assigned</param>
        /// <param name="hasGiftcardHistory">Has gift history within orders; null to load all orders</param>
        /// <returns>Order collection</returns>
        public IList<Order> SearchOrders(DateTime? startTime, DateTime? endTime,
            OrderStatus? os, PaymentStatus[] pss, ShippingStatus? ss, string billingEmail,
            string orderGuid, int[] excludedOrderIds = null, int storeId = 1,
            int? districtId = null, OrderKind? orderKind = null, bool excludeInvoiceAssigned = false,
            bool? hasGiftcardHistory = null)
        {
            var query = QueryCondition(startTime, endTime, os, pss, ss, billingEmail, orderGuid, excludedOrderIds, storeId, districtId, orderKind, excludeInvoiceAssigned, hasGiftcardHistory);

            return query.ToList();
        }


        public IQueryable<Order> QSearchOrders(DateTime? startTime, DateTime? endTime,
            OrderStatus? os, PaymentStatus[] pss, ShippingStatus? ss, string billingEmail,
            string orderGuid, int[] excludedOrderIds = null, int storeId = 1,
            int? districtId = null, OrderKind? orderKind = null, bool excludeInvoiceAssigned = false,
            bool? hasGiftcardHistory = null)
        {
            return QueryCondition(startTime, endTime, os, pss, ss, billingEmail, orderGuid, excludedOrderIds, storeId, districtId, orderKind, excludeInvoiceAssigned, hasGiftcardHistory);;
        }

        /// <summary>
        /// Gets all orders by affiliate identifier
        /// </summary>
        /// <param name="affiliateId">Affiliate identifier</param>
        /// <param name="pageIndex">Page index</param>
        /// <param name="pageSize">Page size</param>
        /// <returns>Orders</returns>
        public virtual IPagedList<Order> GetOrdersByAffiliateId(int affiliateId, int pageIndex, int pageSize)
        {
            var query = _orderRepository.Table;
            query = query.Where(o => !o.Deleted);
            query = query.Where(o => o.AffiliateId.HasValue && o.AffiliateId == affiliateId);
            query = query.OrderByDescending(o => o.CreatedOnUtc);

            var orders = new PagedList<Order>(query, pageIndex, pageSize);
            return orders;
        }

        /// <summary>
        /// Gets all orders by customer identifier
        /// </summary>
        /// <param name="customerId">Customer identifier</param>
        /// <param name="storeId">store identifier, 1 for all store results</param>
        /// <param name="orderKind">order kind</param>
        /// <param name="pageIndex">Page index</param>
        /// <param name="pageSize">Page size</param>
        /// <returns>Order collection</returns>
        public virtual IPagedList<Order> GetOrdersByCustomerId(int customerId, int storeId = 1, OrderKind? orderKind = null, int pageIndex = 0, int pageSize = int.MaxValue, int orderId = -1, DateTime? startTime = null, DateTime? endTime = null,
            OrderStatus? os = null)
        {
            int? orderStatusId = null;
            if (os.HasValue)
                orderStatusId = (int)os.Value;

            var query = from o in _orderRepository.Table
                        orderby o.CreatedOnUtc descending
                        where !o.Deleted && o.CustomerId == customerId
                        select o;

            if (storeId > 1)
            {
                query = from o in query
                        join opv in _opvRepository.Table on o.Id equals opv.OrderId
                        join pv in _pvRepository.Table on opv.ProductVariantId equals pv.Id
                        join p in _productRepository.Table on pv.ProductId equals p.Id
                        where p.StoreId == storeId
                        select o;
            }

            if (orderKind.HasValue)
                query = query.Where(o => o.OrderKindId == (int)orderKind.Value);

            if (orderId > 0)
                query = query.Where(o => o.Id == orderId);
            if (startTime.HasValue)
                query = query.Where(o => startTime.Value <= o.CreatedOnUtc);
            if (endTime.HasValue)
                query = query.Where(o => endTime.Value >= o.CreatedOnUtc);
            if (orderStatusId.HasValue)
                query = query.Where(o => orderStatusId.Value == o.OrderStatusId);

            var orders = query.ToList();

            return new PagedList<Order>(orders, pageIndex, pageSize, orders.Count);
        }

        /// <summary>
        /// Gets Ready Times of shipments within an order (in ascending order)
        /// </summary>
        /// <param name="orderId">Order Id</param>
        /// <returns>Ready Time collection</returns>
        public virtual IList<DateTime> GetReadyTimesByOrder(int orderId)
        {
            var query = from sh in _shipmentRepository.Table
                        where sh.OrderId == orderId
                        select sh;

            var dtList = query.OrderBy(x => x.CreatedOnUtc).Select(x => x.CreatedOnUtc).ToList();
            return dtList;
        }

        /// <summary>
        /// Inserts an order
        /// </summary>
        /// <param name="order">Order</param>
        public virtual void InsertOrder(Order order)
        {
            if (order == null)
                throw new ArgumentNullException("order");

            _orderRepository.Insert(order);

            //event notification
            _eventPublisher.EntityInserted(order);
        }

        /// <summary>
        /// Updates the order
        /// </summary>
        /// <param name="order">The order</param>
        public virtual void UpdateOrder(Order order)
        {
            if (order == null)
                throw new ArgumentNullException("order");

            _orderRepository.Update(order);

            //event notification
            _eventPublisher.EntityUpdated(order);
        }

        /// <summary>
        /// Deletes an order note
        /// </summary>
        /// <param name="orderNote">The order note</param>
        public virtual void DeleteOrderNote(OrderNote orderNote)
        {
            if (orderNote == null)
                throw new ArgumentNullException("orderNote");

            _orderNoteRepository.Delete(orderNote);

            //event notification
            _eventPublisher.EntityDeleted(orderNote);
        }

        /// <summary>
        /// Get an order by authorization transaction ID and payment method system name
        /// </summary>
        /// <param name="authorizationTransactionId">Authorization transaction ID</param>
        /// <param name="paymentMethodSystemName">Payment method system name</param>
        /// <returns>Order</returns>
        public virtual Order GetOrderByAuthorizationTransactionIdAndPaymentMethod(string authorizationTransactionId,
            string paymentMethodSystemName)
        {
            var query = _orderRepository.Table;
            if (!String.IsNullOrWhiteSpace(authorizationTransactionId))
                query = query.Where(o => o.AuthorizationTransactionId == authorizationTransactionId);

            if (!String.IsNullOrWhiteSpace(paymentMethodSystemName))
                query = query.Where(o => o.PaymentMethodSystemName == paymentMethodSystemName);

            query = query.OrderByDescending(o => o.CreatedOnUtc);
            var order = query.FirstOrDefault();
            return order;
        }

        /// <summary>
        /// remove pending and not paid order when order is create 5 hours ago
        /// </summary>
        /// <param name="utcNowTime">utcnow</param>
        public virtual void RemovePendingOrder(DateTime utcNowTime)
        {
            var time = DateTime.UtcNow.AddHours(-5);

            var query = _orderRepository.Table.Where(d => d.PaymentStatusId == 10 && d.OrderStatusId == 10 && d.CreatedOnUtc <= time && !d.Deleted).ToList();

            if (query.Count <= 0) return;

            //delete
            foreach (var order in query)
            {
                DeleteOrder(order);
            }
        }

        #endregion

        #region Orders product variants

        /// <summary>
        /// Gets an order product variant
        /// </summary>
        /// <param name="orderProductVariantId">Order product variant identifier</param>
        /// <returns>Order product variant</returns>
        public virtual OrderProductVariant GetOrderProductVariantById(int orderProductVariantId)
        {
            if (orderProductVariantId == 0)
                return null;

            return _opvRepository.GetById(orderProductVariantId);
        }

        /// <summary>
        /// Gets an order product variant
        /// </summary>
        /// <param name="orderProductVariantGuid">Order product variant identifier</param>
        /// <returns>Order product variant</returns>
        public virtual OrderProductVariant GetOrderProductVariantByGuid(Guid orderProductVariantGuid)
        {
            if (orderProductVariantGuid == Guid.Empty)
                return null;

            var query = from opv in _opvRepository.Table
                        where opv.OrderProductVariantGuid == orderProductVariantGuid
                        select opv;
            var orderProductVariant = query.FirstOrDefault();
            return orderProductVariant;
        }

        /// <summary>
        /// Gets an order product variant list related to shipment 
        /// </summary>
        /// <param name="opvId">Order product variant identifier</param>
        /// <returns>Shipement order product variant list</returns>
        public virtual IList<ShipmentOrderProductVariant> GetShipmentOrderProductVariantListById(int opvId)
        {
            if (opvId == 0)
                return null;

            var query = from sopv in _sopvRepository.Table
                        where sopv.OrderProductVariantId == opvId
                        select sopv;
            return query.ToList();
        }

        /// <summary>
        /// Gets all order product variants
        /// </summary>
        /// <param name="orderId">Order identifier; null to load all records</param>
        /// <param name="customerId">Customer identifier; null to load all records</param>
        /// <param name="startTime">Order start time; null to load all records</param>
        /// <param name="endTime">Order end time; null to load all records</param>
        /// <param name="os">Order status; null to load all records</param>
        /// <param name="ps">Order payment status; null to load all records</param>
        /// <param name="ss">Order shippment status; null to load all records</param>
        /// <param name="loadDownloableProductsOnly">Value indicating whether to load downloadable products only</param>
        /// <returns>Order collection</returns>
        public virtual IList<OrderProductVariant> GetAllOrderProductVariants(int? orderId,
            int? customerId, DateTime? startTime, DateTime? endTime,
            OrderStatus? os, PaymentStatus? ps, ShippingStatus? ss,
            bool loadDownloableProductsOnly)
        {
            int? orderStatusId = null;
            if (os.HasValue)
                orderStatusId = (int)os.Value;

            int? paymentStatusId = null;
            if (ps.HasValue)
                paymentStatusId = (int)ps.Value;

            int? shippingStatusId = null;
            if (ss.HasValue)
                shippingStatusId = (int)ss.Value;


            var query = from opv in _opvRepository.Table
                        join o in _orderRepository.Table on opv.OrderId equals o.Id
                        join pv in _pvRepository.Table on opv.ProductVariantId equals pv.Id
                        where (!orderId.HasValue || orderId.Value == 0 || orderId == o.Id) &&
                        (!customerId.HasValue || customerId.Value == 0 || customerId == o.CustomerId) &&
                        (!startTime.HasValue || startTime.Value <= o.CreatedOnUtc) &&
                        (!endTime.HasValue || endTime.Value >= o.CreatedOnUtc) &&
                        (!orderStatusId.HasValue || orderStatusId == o.OrderStatusId) &&
                        (!paymentStatusId.HasValue || paymentStatusId.Value == o.PaymentStatusId) &&
                        (!shippingStatusId.HasValue || shippingStatusId.Value == o.ShippingStatusId) &&
                        (!loadDownloableProductsOnly || pv.IsDownload) &&
                        !o.Deleted
                        orderby o.CreatedOnUtc descending, opv.Id
                        select opv;

            var orderProductVariants = query.ToList();
            return orderProductVariants;
        }

        /// <summary>
        /// Delete an order product variant
        /// </summary>
        /// <param name="orderProductVariant">The order product variant</param>
        public virtual void DeleteOrderProductVariant(OrderProductVariant orderProductVariant)
        {
            if (orderProductVariant == null)
                throw new ArgumentNullException("orderProductVariant");

            _opvRepository.Delete(orderProductVariant);

            //event notification
            _eventPublisher.EntityDeleted(orderProductVariant);
        }

        /// <summary>
        /// update order Productvaritant
        /// </summary>
        /// <param name="orderProductVariant">orderProductVariant</param>
        public void UpdateOrderProductVariant(OrderProductVariant orderProductVariant)
        {
            if (orderProductVariant == null) return;

            _opvRepository.Update(orderProductVariant);

            //event notification
            _eventPublisher.EntityDeleted(orderProductVariant);
        }

        #endregion

        #region Recurring payments

        /// <summary>
        /// Deletes a recurring payment
        /// </summary>
        /// <param name="recurringPayment">Recurring payment</param>
        public virtual void DeleteRecurringPayment(RecurringPayment recurringPayment)
        {
            if (recurringPayment == null)
                throw new ArgumentNullException("recurringPayment");

            recurringPayment.Deleted = true;
            UpdateRecurringPayment(recurringPayment);
        }

        /// <summary>
        /// Gets a recurring payment
        /// </summary>
        /// <param name="recurringPaymentId">The recurring payment identifier</param>
        /// <returns>Recurring payment</returns>
        public virtual RecurringPayment GetRecurringPaymentById(int recurringPaymentId)
        {
            if (recurringPaymentId == 0)
                return null;

            return _recurringPaymentRepository.GetById(recurringPaymentId);
        }

        /// <summary>
        /// Inserts a recurring payment
        /// </summary>
        /// <param name="recurringPayment">Recurring payment</param>
        public virtual void InsertRecurringPayment(RecurringPayment recurringPayment)
        {
            if (recurringPayment == null)
                throw new ArgumentNullException("recurringPayment");

            _recurringPaymentRepository.Insert(recurringPayment);

            //event notification
            _eventPublisher.EntityInserted(recurringPayment);
        }

        /// <summary>
        /// Updates the recurring payment
        /// </summary>
        /// <param name="recurringPayment">Recurring payment</param>
        public virtual void UpdateRecurringPayment(RecurringPayment recurringPayment)
        {
            if (recurringPayment == null)
                throw new ArgumentNullException("recurringPayment");

            _recurringPaymentRepository.Update(recurringPayment);

            //event notification
            _eventPublisher.EntityUpdated(recurringPayment);
        }

        /// <summary>
        /// Search recurring payments
        /// </summary>
        /// <param name="customerId">The customer identifier; 0 to load all records</param>
        /// <param name="initialOrderId">The initial order identifier; 0 to load all records</param>
        /// <param name="initialOrderStatus">Initial order status identifier; null to load all records</param>
        /// <param name="pageIndex">Page index</param>
        /// <param name="pageSize">Page size</param>
        /// <param name="showHidden">A value indicating whether to show hidden records</param>
        /// <returns>Recurring payment collection</returns>
        public virtual IPagedList<RecurringPayment> SearchRecurringPayments(int customerId,
            int initialOrderId, OrderStatus? initialOrderStatus,
            int pageIndex, int pageSize, bool showHidden = false)
        {
            int? initialOrderStatusId = null;
            if (initialOrderStatus.HasValue)
                initialOrderStatusId = (int)initialOrderStatus.Value;

            var query1 = from rp in _recurringPaymentRepository.Table
                         join c in _customerRepository.Table on rp.InitialOrder.CustomerId equals c.Id
                         where
                         (!rp.Deleted) &&
                         (showHidden || !rp.InitialOrder.Deleted) &&
                         (showHidden || !c.Deleted) &&
                         (showHidden || rp.IsActive) &&
                         (customerId == 0 || rp.InitialOrder.CustomerId == customerId) &&
                         (initialOrderId == 0 || rp.InitialOrder.Id == initialOrderId) &&
                         (!initialOrderStatusId.HasValue || initialOrderStatusId.Value == 0 || rp.InitialOrder.OrderStatusId == initialOrderStatusId.Value)
                         select rp.Id;

            var query2 = from rp in _recurringPaymentRepository.Table
                         where query1.Contains(rp.Id)
                         orderby rp.StartDateUtc, rp.Id
                         select rp;

            var recurringPayments = new PagedList<RecurringPayment>(query2, pageIndex, pageSize);
            return recurringPayments;
        }

        #endregion

        #region Return requests

        /// <summary>
        /// Deletes a return request
        /// </summary>
        /// <param name="returnRequest">Return request</param>
        public virtual void DeleteReturnRequest(ReturnRequest returnRequest)
        {
            if (returnRequest == null)
                throw new ArgumentNullException("returnRequest");

            _returnRequestRepository.Delete(returnRequest);

            //event notification
            _eventPublisher.EntityDeleted(returnRequest);
        }

        /// <summary>
        /// Gets a return request
        /// </summary>
        /// <param name="returnRequestId">Return request identifier</param>
        /// <returns>Return request</returns>
        public virtual ReturnRequest GetReturnRequestById(int returnRequestId)
        {
            if (returnRequestId == 0)
                return null;

            return _returnRequestRepository.GetById(returnRequestId);
        }

        /// <summary>
        /// Search return requests
        /// </summary>
        /// <param name="customerId">Customer identifier; null to load all entries</param>
        /// <param name="orderProductVariantId">Order product variant identifier; null to load all entries</param>
        /// <param name="rs">Return request status; null to load all entries</param>
        /// <param name="pageIndex">Page index</param>
        /// <param name="pageSize">Page size</param>
        /// <param name="storeId">store identifier, 1 for all store results</param>
        /// <returns>Return requests</returns>
        public virtual IPagedList<ReturnRequest> SearchReturnRequests(int customerId,
            int orderProductVariantId, ReturnRequestStatus? rs,
            int pageIndex, int pageSize, int storeId = 1)
        {
            var query = _returnRequestRepository.Table;

            if (storeId > 1)
            {
                query = from rr in query
                        join opv in _opvRepository.Table on rr.OrderProductVariantId equals opv.Id
                        join pv in _pvRepository.Table on opv.ProductVariantId equals pv.Id
                        join p in _productRepository.Table on pv.ProductId equals p.Id
                        where p.StoreId == storeId
                        select rr;
            }

            if (customerId > 0)
                query = query.Where(rr => customerId == rr.CustomerId);
            if (rs.HasValue)
            {
                int returnStatusId = (int)rs.Value;
                query = query.Where(rr => rr.ReturnRequestStatusId == returnStatusId);
            }
            if (orderProductVariantId > 0)
                query = query.Where(rr => rr.OrderProductVariantId == orderProductVariantId);

            query = query.OrderByDescending(rr => rr.CreatedOnUtc).ThenByDescending(rr => rr.Id);


            var returnRequests = new PagedList<ReturnRequest>(query, pageIndex, pageSize);
            return returnRequests;
        }

        /// <summary>
        /// Verify if the return request belongs to the store.
        /// </summary>
        /// <param name="orderProductVariantId">Order product variant identifier</param>
        /// <param name="storeId">store identifier</param>
        /// <returns></returns>
        public bool IsReturnRequestBelongStore(int orderProductVariantId, int storeId)
        {
            if (storeId > 1)
            {
                var query = from rr in _returnRequestRepository.Table
                            where rr.OrderProductVariantId == orderProductVariantId
                            join opv in _opvRepository.Table on rr.OrderProductVariantId equals opv.Id
                            join pv in _pvRepository.Table on opv.ProductVariantId equals pv.Id
                            join p in _productRepository.Table on pv.ProductId equals p.Id
                            where p.StoreId == storeId
                            select rr;
                if (query.FirstOrDefault() != null)
                    return true;
                else
                    return false;
            }
            else
                return true;
        }

        #endregion

        #region Order group

        /// <summary>
        /// Gets the order group by group GUID
        /// </summary>
        /// <param name="orderGroupId">group GUID</param>
        /// <param name="customerId">optional customer identifier</param>
        /// <returns>Order group</returns>
        public virtual IList<Order> GetOrderGroupByGroupId(Guid orderGroupId, int? customerId = null)
        {
            var query = from o in _orderRepository.Table
                        orderby o.CreatedOnUtc descending
                        where !o.Deleted && o.OrderGroupGuid == orderGroupId
                        select o;

            if (customerId.HasValue)
                query = query.Where(o => o.CustomerId == customerId.Value);

            return query.ToList();
        }

        /// <summary>
        /// Gets the order group by order identifier
        /// </summary>
        /// <param name="orderId">order identifier</param>
        /// <param name="customerId">optional customer identifier</param>
        /// <returns>Order group</returns>
        public virtual IList<Order> GetOrderGroupByOrderId(int orderId, int? customerId = null)
        {
            var guid = from o in _orderRepository.Table
                       where orderId == o.Id && o.OrderGroupGuid.HasValue
                       select o.OrderGroupGuid;

            var query = from o in _orderRepository.Table
                        select o;

            if (customerId.HasValue)
                query = query.Where(o => o.CustomerId == customerId.Value);

            query = from o in query
                    where !o.Deleted && ((o.Id == orderId) || (o.OrderGroupGuid.HasValue && guid.Contains(o.OrderGroupGuid)))
                    select o;

            return query.ToList();
        }

        /// <summary>
        /// Gets the grouped orders by customer
        /// </summary>
        /// <param name="customerId">customer identifier</param>
        /// <param name="orderKind">optional order kind</param>
        /// <returns>Grouped orders. first order is the key order</returns>
        public virtual IList<OrderGroup> GetOrderGroupByCustomerId(int customerId, OrderKind? orderKind = null)
        {
            var query = from o in _orderRepository.Table
                        orderby o.CreatedOnUtc descending
                        where !o.Deleted && o.CustomerId == customerId
                        select o;

            if (orderKind.HasValue)
                query = query.Where(o => o.OrderKindId == (int)orderKind.Value);

            var groups = from o in query
                         group o by o.OrderGroupGuid into g
                         where g.Key.HasValue && g.Count() > 0
                         select new OrderGroup
                         {
                             OrderGroupGuid = g.Key.Value,
                             //Orders = g.OrderByDescending(o => o.Id).ToList()  //it dosen't returns first record. 
                             Orders = g.OrderBy(o => o.Id).ToList()
                         };
            groups = groups.OrderByDescending(g => g.Orders.FirstOrDefault().CreatedOnUtc);
            return groups.ToList();
        }

        #endregion

        #endregion
    }
}
